package org.matheclipse.core.preprocessor;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Set;

import org.matheclipse.core.convert.AST2Expr;
import org.matheclipse.core.expression.F;
import org.matheclipse.core.interfaces.IAST;
import org.matheclipse.core.interfaces.IExpr;
import org.matheclipse.core.interfaces.ISymbol;
import org.matheclipse.core.patternmatching.PatternMatcher;
import org.matheclipse.core.patternmatching.RulesData;
import org.matheclipse.parser.client.Parser;
import org.matheclipse.parser.client.ast.ASTNode;

/**
 * Generate java sources for Symja rule files.
 */
public class RulePreprocessor {

	final static String HEADER = "package org.matheclipse.core.reflection.system.rules;\n" + "\n"
			+ "import static org.matheclipse.core.expression.F.*;\n" + "import org.matheclipse.core.interfaces.IAST;\n"
			+ "\n" + "/**\n"
			+ " * Generated by <code>org.matheclipse.core.preprocessor.RulePreprocessor</code>.<br />\n"
			+ " * See GIT repository at: <a href=\"https://bitbucket.org/axelclk/symja_android_library\">https://bitbucket.org/axelclk/symja_android_library under the tools directory</a>.\n"
			+ " */\n" + "public interface ";

	final static String SIZES = "  /**\n" + "   * <ul>\n"
			+ "   * <li>index 0 - number of equal rules in <code>RULES</code></li>\n" + "	 * </ul>\n" + "	 */\n"
			+ "  final public static int[] SIZES = { ";

	final static String LIST0 = "  final public static IAST RULES";
	final static String LIST1 = " = List(";

	final static String FOOTER0 = "  );\n";
	final static String FOOTER1 = "}";

	public RulePreprocessor() {
	}

	public static void appendSetDelayedToBuffer(IAST ast, StringBuffer buffer, boolean evalRHS, boolean last) {
		IExpr leftHandSide = ast.arg1();
		IExpr rightHandSide = ast.arg2();
		if (ast.arg1().isAST()) {
			leftHandSide = PatternMatcher.evalLeftHandSide((IAST) leftHandSide);
		}
		if (evalRHS) {
			rightHandSide = F.eval(rightHandSide);
		}
		buffer.append(leftHandSide.internalJavaString(false, 1, false));
		buffer.append(",\n      ");
		buffer.append(rightHandSide.internalJavaString(false, 1, false));
		if (last) {
			buffer.append(")\n");
		} else {
			buffer.append("),\n");
		}
	}

	public static void convert(ASTNode node, String rulePostfix, StringBuffer buffer, final PrintWriter out,
			String symbolName) {
		try {
			// convert ASTNode to an IExpr node
			IExpr expr = AST2Expr.CONST.convert(node);
			if (expr.isListOfLists()) {
				IAST list = (IAST) expr;
				for (int i = 1; i < list.size(); i++) {
					convertExpr((IExpr) list.get(i), Integer.toString(i), out, null);
				}
			} else {
				convertExpr(expr, rulePostfix, out, symbolName);
			}
		} catch (UnsupportedOperationException uoe) {
			System.out.println(uoe.getMessage());
			System.out.println(node.toString());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void convertExpr(IExpr expr, String rulePostfix, final PrintWriter out, String symbolName) {
		boolean last;
		StringBuffer buffer = new StringBuffer();
		Set<ISymbol> headerSymbols = new HashSet<ISymbol>();
		if (expr.isAST()) {
			IAST list = (IAST) expr;
			if (symbolName != null) {
				int equalsRuleCounter = 0;
				int simpleRuleCounter = 0;
				for (int i = 1; i < list.size(); i++) {
					last = i == (list.size() - 1);
					expr = (IExpr) list.get(i);
					if (expr.isAST(F.SetDelayed, 3)) {
						IAST ast = (IAST) expr;
						if (!RulesData.isComplicatedPatternRule(ast.arg1(), headerSymbols)) {
							simpleRuleCounter++;
						}
					} else if (expr.isAST(F.Set, 3)) {
						equalsRuleCounter++;
					}
				}
				if (equalsRuleCounter > 0 || simpleRuleCounter > 0) {
					out.print(SIZES);
					out.append(Integer.toString(equalsRuleCounter));
					out.append(", ");
					out.append(Integer.toString(simpleRuleCounter));
					out.append(" };\n\n");
					buffer.append("    IInit(");
					buffer.append(symbolName);
					buffer.append(", SIZES),\n");
				}
			}

			for (int i = 1; i < list.size(); i++) {
				last = i == (list.size() - 1);
				expr = (IExpr) list.get(i);
				if (expr.isAST(F.SetDelayed, 3)) {
					IAST ast = (IAST) expr;
					buffer.append("    ISetDelayed(");
					appendSetDelayedToBuffer(ast, buffer, false, last);
				} else if (expr.isAST(F.Set, 3)) {
					IAST ast = (IAST) expr;
					buffer.append("    ISet(");
					appendSetDelayedToBuffer(ast, buffer, true, last);
				} else if (expr.isAST(F.Rule, 3)) {
					IAST ast = (IAST) expr;
					buffer.append("    Rule(");
					appendSetDelayedToBuffer(ast, buffer, true, last);
				}
			}
		} else {
			if (expr.isAST(F.SetDelayed, 3)) {
				IAST ast = (IAST) expr;
				buffer.append("    ISetDelayed(");
				appendSetDelayedToBuffer(ast, buffer, false, true);
			} else if (expr.isAST(F.Set, 3)) {
				IAST ast = (IAST) expr;
				buffer.append("    ISet(");
				appendSetDelayedToBuffer(ast, buffer, true, true);
			} else if (expr.isAST(F.Rule, 3)) {
				IAST ast = (IAST) expr;
				buffer.append("    Rule(");
				appendSetDelayedToBuffer(ast, buffer, true, true);
			}
		}
		out.print(LIST0);
		out.print(rulePostfix);
		out.println(LIST1);
		out.print(buffer.toString());
		out.print(FOOTER0);
	}

	public static ASTNode parseFileToList(File file) {
		try {
			final BufferedReader f = new BufferedReader(new FileReader(file));
			final StringBuffer buff = new StringBuffer(1024);
			String line;
			while ((line = f.readLine()) != null) {
				buff.append(line);
				buff.append('\n');
			}
			f.close();
			String inputString = buff.toString();
			Parser p = new Parser(true, false);
			return p.parse(inputString);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Generate Java files (*.java) from Symja rule files (*.m)
	 * 
	 * @param sourceLocation
	 *            source directory for rule (*.m) files
	 * @param targetLocation
	 *            target directory for the generated Java files
	 * @param ignoreTimestamp
	 *            if <code>false</code> only change the target file (*.java), if
	 *            the source file (*.m) has a newer time stamp than the target
	 *            file.
	 */
	public static void generateFunctionStrings(final File sourceLocation, File targetLocation,
			boolean ignoreTimestamp) {
		if (sourceLocation.exists()) {
			// Get the list of the files contained in the package
			final String[] files = sourceLocation.list();
			if (files != null) {
				StringBuffer buffer;
				for (int i = 0; i < files.length; i++) {
					File sourceFile = new File(sourceLocation, files[i]);
					// we are only interested in .m files
					if (files[i].endsWith(".m")) {
						ASTNode node = parseFileToList(sourceFile);

						if (node != null) {
							buffer = new StringBuffer(100000);
							PrintWriter out;
							try {
								String className = files[i].substring(0, files[i].length() - 2);
								String symbolName = className.substring(0, className.length() - 5);
								File targetFile = new File(targetLocation, className + ".java");
								if (targetFile.exists()) {
									if (!ignoreTimestamp && (sourceFile.lastModified() <= targetFile.lastModified())) {
										// only copy if timestamp is newer than
										// existing ones
										continue;
									}
								}
								System.out.println(className);
								out = new PrintWriter(targetFile.getCanonicalPath());
								out.print(HEADER);
								out.print(className);
								out.print(" {\n");
								convert(node, "", buffer, out, symbolName);
								out.println(FOOTER1);
								out.close();
							} catch (IOException e) {
								e.printStackTrace();
							}
						}

					}
				}
			}
		}

	}

	public static void main(final String[] args) {
		F.initSymbols();
		File sourceLocation = new File("C:\\Users\\dev\\git\\symja_android_library\\symja_android_library\\rules");
		File targetLocation = new File(
				"C:\\Users\\dev\\git\\symja_android_library\\symja_android_library\\matheclipse-core\\src\\main\\java\\org\\matheclipse\\core\\reflection\\system\\rules");

		generateFunctionStrings(sourceLocation, targetLocation, true);
	}

}